using Unity.Burst; // 引入 Burst 编译相关的命名空间，用于高性能并行计算
using Unity.Collections; // 提供 Native 容器（如 NativeArray），用于 Job 系统
using Unity.Jobs; // Unity 的 Job 系统，用于多线程任务调度
using Unity.Mathematics; // 提供数学类型，如 float3, quaternion 等
using UnityEngine; // Unity 主要 API

using static Unity.Mathematics.math; // 允许直接使用 math 命名空间下的方法，如 mul(), float3(), 等
using quaternion = Unity.Mathematics.quaternion; // 定义 quaternion 别名

public class Fractal : MonoBehaviour
{

	// 表示分形中的一个组成部分（子部件）
	struct FractalPart
	{
		public float3 direction, worldPosition; // 方向向量、世界坐标位置
		public quaternion rotation, worldRotation; // 自身旋转、世界旋转
		public float spinAngle; // 自旋角度
	}

	/// <summary>
	/// 让 Unity 的 ​​Burst 编译器​​ 对该 Job 的代码进行 ​​高度优化编译​​，
	/// 生成接近底层 C++ / SIMD 级别的 ​​原生机器码
	/// FloatPrecision = FloatPrecision.Standard，指定 ​​浮点数运算的精度级别​​。
	/// FloatMode = FloatMode.Fast，指定 ​​浮点运算的行为模式​​，即是否允许 Burst 进行一些 ​​激进的数学优化​​。
	/// CompileSynchronously = true，指定 ​​是否同步编译​​。
	/// </summary>
	[BurstCompile(FloatPrecision.Standard, FloatMode.Fast, CompileSynchronously = true)]
	struct UpdateFractalLevelJob : IJobFor
	{

		public float spinAngleDelta; // 每帧旋转角度增量
		public float scale; // 当前层级的缩放比例

		// 只读：父级分形部件数组
		[ReadOnly]
		public NativeArray<FractalPart> parents;

		// 要更新的当前层级的分形部件数组
		public NativeArray<FractalPart> parts;

		// 输出：当前层级所有部件的世界变换矩阵（float3x4 格式）
		[WriteOnly]
		public NativeArray<float3x4> matrices;

		// 执行每个索引对应的任务（IJobFor 是针对索引并行的 Job）
		public void Execute(int i)
		{
			// 获取当前部件的父级
			FractalPart parent = parents[i / 5]; // 每个父级有 5 个子部件

			// 获取当前部件
			FractalPart part = parts[i];

			// 更新当前部件的自旋角度
			part.spinAngle += spinAngleDelta;

			// 计算当前部件的世界旋转：父级旋转 × 当前部件自身旋转 × 绕 Y 轴的自旋旋转
			part.worldRotation = mul(parent.worldRotation,
				mul(part.rotation, quaternion.RotateY(part.spinAngle))
			);

			// 计算当前部件的世界位置：父级位置 + （父级旋转 × 方向向量 × 1.5倍缩放）
			part.worldPosition =
				parent.worldPosition +
				mul(parent.worldRotation, 1.5f * scale * part.direction);

			// 保存更新后的部件数据
			parts[i] = part;

			// 构造用于渲染的变换矩阵 float3x4（旋转矩阵 + 位移向量）
			float3x3 r = float3x3(part.worldRotation) * scale; // 旋转矩阵并应用缩放
			matrices[i] = float3x4(r.c0, r.c1, r.c2, part.worldPosition); // 组合成 float3x4 矩阵
		}
	}

	// 用于在 Shader 中传递实例化矩阵的属性 ID
	static readonly int matricesId = Shader.PropertyToID("_Matrices");

	// 定义了分形中每个子部件可能的 5 个方向（上、右、左、前、后），
	// 这些方向是单位向量，用于确定子部件相对于父部件的生成位置。
	static float3[] directions = {
		up(),    // (0, 1, 0) —— 向上的方向
		right(), // (1, 0, 0) —— 向右的方向
		left(),  // (-1, 0, 0) —— 向左的方向
		forward(), // (0, 0, 1) —— 向前的方向
		back()   // (0, 0, -1) —— 向后的方向
	};

	// 定义了分形中每个子部件可能的 5 个初始旋转（四元数），
	// 对应于每个方向上的“自然朝向”，让子部件面向该方向。
	static quaternion[] rotations = {
		quaternion.identity,                          // 无旋转（默认朝向）
		quaternion.RotateZ(-0.5f * PI),               // 绕 Z 轴 向左转 90 度（面向左边）
		quaternion.RotateZ(0.5f * PI),                // 绕 Z 轴 向右转 90 度（面向右边）
		quaternion.RotateX(0.5f * PI),                // 绕 X 轴 向上转 90 度（面向前面）
		quaternion.RotateX(-0.5f * PI)                // 绕 X 轴 向下转 90 度（面向后面）
	};

	// 定义了一个 静态的 MaterialPropertyBlock，用于高效地设置 Shader 参数（比如实例化矩阵），
	// 避免为每个对象单独设置 Material 属性，提升渲染性能。
	static MaterialPropertyBlock propertyBlock;

	// 分形的递归深度，即最大层级数，范围 1~8
	[SerializeField, Range(1, 8)]
	int depth = 4;

	// 分形的网格和材质
	[SerializeField]
	Mesh mesh;

	[SerializeField]
	Material material;

	// 存储每一层级的分形部件数据（NativeArray）
	NativeArray<FractalPart>[] parts;

	// 存储每一层级的变换矩阵（NativeArray<float3x4>）
	NativeArray<float3x4>[] matrices;

	// 用于 GPU Instancing 绘制的 ComputeBuffer 数组
	ComputeBuffer[] matricesBuffers;

	// 当启用此 MonoBehaviour 时调用（比如场景加载或脚本启用）
	void OnEnable()
	{
		// 初始化每一层的 NativeArray 和 ComputeBuffer
		parts = new NativeArray<FractalPart>[depth];
		matrices = new NativeArray<float3x4>[depth];
		matricesBuffers = new ComputeBuffer[depth];
		int stride = 12 * 4; // 每个 float3x4 占 12 个 float，每个 float 4 字节，共 48 字节


		for (int i = 0, length = 1; i < parts.Length; i++, length *= 5)
		{ // 每层部件数量为 5^层级
			parts[i] = new NativeArray<FractalPart>(length, Allocator.Persistent); // 创建部件数据
			matrices[i] = new NativeArray<float3x4>(length, Allocator.Persistent); // 创建矩阵数据
			matricesBuffers[i] = new ComputeBuffer(length, stride); // 创建 GPU 缓冲区
		}

		// 初始化根节点（第 0 层第 0 个部件）
		parts[0][0] = CreatePart(0);

		// 初始化其他层级的部件
		for (int li = 1; li < parts.Length; li++)
		{
			// 遍历每一层（从第1层开始）
			NativeArray<FractalPart> levelParts = parts[li];

			for (int fpi = 0; fpi < levelParts.Length; fpi += 5)
			{
				// 每 5 个为一组，对应同一个父节点
				for (int ci = 0; ci < 5; ci++)
				{
					// 每组 5 个子节点
					levelParts[fpi + ci] = CreatePart(ci); // 创建子部件
				}
			}
		}

		// 初始化 MaterialPropertyBlock，用于高效设置 Shader 参数
		propertyBlock ??= new MaterialPropertyBlock();
	}

	// 当禁用此 MonoBehaviour 时调用（比如场景卸载或脚本禁用）
	void OnDisable()
	{
		// 释放所有 ComputeBuffer 和 NativeArray，防止内存泄漏
		for (int i = 0; i < matricesBuffers.Length; i++)
		{
			if (matricesBuffers[i] != null) matricesBuffers[i].Release(); // 释放 GPU 缓冲区
			if (parts[i] != null) parts[i].Dispose(); // 释放 NativeArray
			if (matrices[i] != null) matrices[i].Dispose();
		}
		// 清空引用
		parts = null;
		matrices = null;
		matricesBuffers = null;
	}

	// 当在 Inspector 中修改参数时调用
	void OnValidate()
	{
		// 如果已经初始化并且当前是启用状态，则重新初始化（响应参数变化）
		if (parts != null && enabled)
		{
			OnDisable();
			OnEnable();
		}
	}

	// 创建一个分形部件，根据子节点索引设置方向和旋转
	FractalPart CreatePart(int childIndex) => new FractalPart
	{
		direction = directions[childIndex], // 设置方向向量
		rotation = rotations[childIndex]    // 设置旋转四元数
	};

	// 每帧更新函数
	void Update()
	{
		// 计算每帧自旋角度增量
		float spinAngleDelta = 0.125f * PI * Time.deltaTime;

		// 获取根节点（第0层第0个部件）
		FractalPart rootPart = parts[0][0];

		// 更新根节点的自旋角度（基于时间增量）
		rootPart.spinAngle += spinAngleDelta;

		// 计算根节点的世界旋转：当前物体旋转 × 部件自身旋转 × 绕 Y 轴的自旋
		rootPart.worldRotation = mul(transform.rotation,
			mul(rootPart.rotation, quaternion.RotateY(rootPart.spinAngle))
		);

		// 根节点的世界位置就是当前物体的位置
		rootPart.worldPosition = transform.position;

		// 保存更新后的根节点
		parts[0][0] = rootPart;

		// 计算根节点的变换矩阵（用于渲染）
		float objectScale = transform.lossyScale.x; // 获取物体的实际缩放值
		float3x3 r = float3x3(rootPart.worldRotation) * objectScale; // 应用旋转与缩放
		matrices[0][0] = float3x4(r.c0, r.c1, r.c2, rootPart.worldPosition); // 构造矩阵

		// 当前层级的缩放值
		float scale = objectScale;

		// 用于并行任务的句柄，确保任务按顺序执行
		JobHandle jobHandle = default;

		// 从第 1 层开始，逐层更新分形部件和矩阵
		for (int li = 1; li < parts.Length; li++)
		{
			scale *= 0.5f; // 每下一层缩放减半
						   // 调度一个并行 Job，更新第 li 层的所有部件
			jobHandle = new UpdateFractalLevelJob
			{
				spinAngleDelta = spinAngleDelta,
				scale = scale,
				parents = parts[li - 1], // 父级是上一层的部件
				parts = parts[li],       // 当前层部件
				matrices = matrices[li]  // 当前层输出矩阵
			}.ScheduleParallel(parts[li].Length, 5, jobHandle); // 并行度为 5，即每次处理 5 个
		}

		// 等待所有 Job 完成
		jobHandle.Complete();

		// 定义包围盒，用于实例化绘制的视锥剔除
		var bounds = new Bounds(rootPart.worldPosition, 3f * objectScale * Vector3.one);

		// 遍历每一层，将矩阵数据上传到 GPU 并进行绘制
		for (int i = 0; i < matricesBuffers.Length; i++)
		{
			ComputeBuffer buffer = matricesBuffers[i];
			buffer.SetData(matrices[i]); // 将 CPU 数据拷贝到 GPU Buffer

			// 设置 MaterialPropertyBlock 中的缓冲区参数
			propertyBlock.SetBuffer(matricesId, buffer);

			// 使用 GPU Instancing 绘制该层级的所有实例（网格、材质、包围盒、实例数量）
			Graphics.DrawMeshInstancedProcedural(
				mesh, 0, material, bounds, buffer.count, propertyBlock
			);
		}
	}
}